package com.apache.zuul.route;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.apache.cache.service.impl.LoadCacheFactory;
import com.apache.tools.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.*;

/**
 * @description: 实现动态刷新路由
 * @author: Hou Dayu
 * @date: Created in 2020/3/17
 */
public class CustomRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {

    public final static Logger logger = LoggerFactory.getLogger(CustomRouteLocator.class);

    private ZuulProperties properties;

    private Map<String, String> paths = new HashMap<>();
    private Map<String, String> loginUrlMap = new HashMap<>();

    public CustomRouteLocator(String servletPath, ZuulProperties properties) {
        super(servletPath, properties);
        this.properties = properties;
        logger.info("servletPath:{}",servletPath);
    }

    @Override
    public void refresh() {
        doRefresh();
    }

    /**
     * 根据服务id获取配置的loginUrl地址
     * @param serviceId
     * @return
     */
    public String getCacheRouteLoginUrl(String serviceId){
        return loginUrlMap.get(serviceId);
    }

    @Override
    protected Map<String, ZuulRoute> locateRoutes() {
        LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
        //从application.properties中加载路由信息
        routesMap.putAll(super.locateRoutes());
        //从缓存中加载路由信息
        routesMap.putAll(locateRoutesFromCache());
        //优化一下配置
        LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
        for (Map.Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            // Prepend with slash if not already present.
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            if (StringUtils.hasText(this.properties.getPrefix())) {
                path = this.properties.getPrefix() + path;
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
            }
            if(StrUtil.isNotNull(entry.getValue().getServiceId())) {
                paths.put(entry.getValue().getServiceId().toLowerCase(), path);
            }else{
                paths.put(entry.getValue().getUrl().toLowerCase(), path);
            }
            values.put(path, entry.getValue());
        }
        return values;
    }

    public String getPath(String serverId){
        return paths.get(serverId);
    }

    private Map<String, ZuulRoute> locateRoutesFromCache(){
        Map<String, ZuulRoute> routes = new LinkedHashMap<>();
        try {
            List<String> keys = LoadCacheFactory.getInstance().getCacheManager("zuul.routes").getAllKeys();
            if (null == keys || keys.isEmpty()) {
                return routes;
            }
            List<Object> objs = LoadCacheFactory.getInstance().getCacheManager("zuul.routes").getObjects(keys);
            for (Object result : objs) {
                Map<String, String> route = (Map) result;
                if (StrUtil.isNull(route.get("path")) || (StrUtil.isNull(route.get("url")) && StrUtil.isNull(route.get("serviceId")))) {
                    continue;
                }
                String urlStr = route.get("url");
                if(!"serviceid".equalsIgnoreCase(route.get("routeType")) && urlStr.indexOf(",") > -1) {//URL方式，多路径配置
                    String url[] = route.get("url").split(",");
                    for(int i=0;i<url.length;i++) {
                        ZuulRoute zuulRoute = new ZuulRoute();
                        try {
                            zuulRoute.setId(route.get("infoId"));
                            zuulRoute.setUrl(url[i]);
                            zuulRoute.setPath(route.get("path"));
                            boolean strPrefix = Boolean.parseBoolean(StrUtil.doNull(route.get("stripPrefix"), "true"));
                            zuulRoute.setStripPrefix(strPrefix);
                            boolean retryable = Boolean.parseBoolean(StrUtil.doNull(route.get("retryable"), "false"));
                            zuulRoute.setRetryable(retryable);
                            Set<String> headers = new LinkedHashSet();
                            headers.add("*");
                            zuulRoute.setSensitiveHeaders(headers);
                            loginUrlMap.put(url[i].toLowerCase(), route.get("loginUrl"));
                        } catch (Exception e) {
                            logger.error("=============load zuul route info from db with error==============", e);
                        }
                        routes.put(zuulRoute.getPath(), zuulRoute);
                    }
                } else {
                    ZuulRoute zuulRoute = new ZuulRoute();
                    try {
                        zuulRoute.setId(route.get("infoId"));
                        if ("serviceid".equalsIgnoreCase(route.get("routeType"))) {
                            if (StrUtil.isNotNull(route.get("serviceId")))
                                zuulRoute.setServiceId(route.get("serviceId"));
                        } else {
                            if (StrUtil.isNotNull(route.get("url")))
                                zuulRoute.setUrl(route.get("url"));
                        }
                        zuulRoute.setPath(route.get("path"));
                        boolean strPrefix = Boolean.parseBoolean(StrUtil.doNull(route.get("stripPrefix"), "true"));
                        zuulRoute.setStripPrefix(strPrefix);
                        boolean retryable = Boolean.parseBoolean(StrUtil.doNull(route.get("retryable"), "false"));
                        zuulRoute.setRetryable(retryable);
                        Set<String> headers = new LinkedHashSet();
                        headers.add("*");
                        zuulRoute.setSensitiveHeaders(headers);
                        if (StrUtil.isNotNull(route.get("serviceId"))) {
                            loginUrlMap.put(route.get("serviceId").toLowerCase(), route.get("loginUrl"));
                        } else {
                            loginUrlMap.put(route.get("url").toLowerCase(), route.get("loginUrl"));
                        }
                        routes.put(zuulRoute.getPath(), zuulRoute);
                    } catch (Exception e) {
                        logger.error("=============load zuul route info from db with error==============", e);
                    }
                }
                String cacheKey = route.get("serviceId");
                if (StrUtil.isNull(cacheKey) && StrUtil.isNotNull(route.get("url"))) {
                    cacheKey = route.get("url");
                }
                setRoutsCache("",cacheKey, route);
            }
        }catch (Exception e){

        }
        return routes;
    }

    private JedisPool jedisPool;

    public void setJedisPool(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }

    public void setRoutsCache(String spaceName,String cacheKey, Map<String, String> route){
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            jedis.select(10);
            jedis.hsetnx(StrUtil.doNull(spaceName,"zuul.routs"),cacheKey, JSON.toJSONString(route));
        }catch (Exception e){
        }finally{
            if (null != jedis) {
                jedis.close();
            }
        }
    }

    public JSONObject getRoute(String spaceName, String serviceId){
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            jedis.select(10);
            String result = jedis.hget(StrUtil.doNull(spaceName,"zuul.routs"),serviceId);
            if(StrUtil.isNotNull(result)){
                return JSON.parseObject(result);
            }
        }catch (Exception e){
        }finally{
            if (null != jedis) {
                jedis.close();
            }
        }
        return new JSONObject();
    }

    public void buildCacheInfo(String key, String value, int expireTime){
        Jedis jedis = null;
        boolean mark = true;
        try{
            jedis = jedisPool.getResource();
            jedis.select(1);
            jedis.del(key);
            jedis.setnx(key,value);
            if(expireTime>0)
                jedis.expire(key,expireTime);
        }catch (Exception e){
        }finally{
            if (null != jedis) {
                jedis.close();
            }
        }
    }

    public String getCahceInfo(String key){
        if(StrUtil.isNull(key)){
            return null;
        }
        Jedis jedis = null;
        try{
            jedis = jedisPool.getResource();
            jedis.select(1);
            return jedis.get(key);
        }catch (Exception e){
            return null;
        }finally{
            if (null != jedis) {
                jedis.close();
            }
        }
    }
}
